//
//  RunLoopViewController.m
//  ObjectCProject
//
//  Created by lvfeijun on 2021/5/24.
//

#import "RunLoopViewController.h"

@interface RunLoopViewController ()

@property (nonatomic,strong) dispatch_source_t dispatch_source_timer;

@end

@implementation RunLoopViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
}
-(NSArray *)getDatas{
    return @[@"runloop6Status",@"dispatch_source_t"];
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    [self tableViewSelcctedAction:indexPath];
}

#pragma mark runloop监听的六个状态
-(void)runloop6Status{
    // 创建Observer
    /*
     第1个参数: 指定如何给observer分配存储空间
     第2个参数: 需要监听的状态类型/ kCFRunLoopAllActivities监听所有状态
     第3个参数: 是否每次都需要监听
     第4个参数: 优先级
     第5个参数: 监听到状态改变之后的回调
     */
    CFRunLoopObserverRef  observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即将进入runloop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即将处理timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即将处理source");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即将进入睡眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"刚从睡眠中唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"即将退出");
                break;
            default:
                break;
        }
    });
    // 给主线程的RunLoop添加一个观察者
    /*
     第1个参数: 需要给哪个RunLoop添加观察者
     第2个参数: 需要添加的Observer对象
     第3个参数: 在哪种模式下可以可以监听
     */
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
 
    // 释放对象
    CFRelease(observer);
    [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
}
- (void)show{
    NSLog(@"%s",__func__);
}


#pragma mark dispatch_source_t 定时器
/*
 Dispatch Source Timer
 是一种与 Dispatch Queue 结合使用的定时器。当需要在后台 queue 中定期执行任务的时候，使用 Dispatch Source Timer 要比使用 NSTimer 更加自然，也更加高效（无需在 main queue 和后台 queue 之前切换）。

 dispatch_suspend
 当暂停派发队列时需要注意，调用 dispatch_suspend 暂停一个队列，并不意味着暂停当前正在执行的 block，而是 block 可以执行完，但是接下来的 block 会被暂停，直到 dispatch_resume 被调用。
 
 dispatch_resume
 dispatch_suspend 状态下释放,会发生崩溃并报“EXC_BAD_INSTRUCTION”错误
 
 初始状态(未调用dispatch_resume)、挂起状态，都不能直接调用dispatch_source_cancel(timer)，调用就会导致app闪退。
 建议一：尽量不使用dispatch_suspend，在dealloc方法中，在dispatch_resume状态下直接使用dispatch_source_cancel来取消定时器。
 建议二：使用懒加载创建定时器，并且记录当timer 处于dispatch_suspend的状态。这些时候，只要在 调用dealloc 时判断下，已经调用过 dispatch_suspend 则再调用下 dispatch_resume后再cancel，然后再释放timer。
 
 总结：
 Dispatch Source使用最多的就是用来实现定时器，source创建后默认是暂停状态，需要手动调用dispatch_resume启动定时器。
 
 Dispatch Source定时器使用时也有一些需要注意的地方，不然很可能会引起crash：
 1、循环引用：因为dispatch_source_set_event_handler回调是个block，在添加到source的链表上时会执行copy并被source强引用，如果block里持有了self，self又持有了source的话，就会引起循环引用。正确的方法是使用weak+strong或者提前调用dispatch_source_cancel取消timer。
 2、dispatch_resume和dispatch_suspend调用次数需要平衡，如果重复调用dispatch_resume则会崩溃,因为重复调用会让dispatch_resume代码里if分支不成立，从而执行了DISPATCH_CLIENT_CRASH("Over-resume of an object")导致崩溃。
 3、source在suspend状态下，如果直接设置source = nil或者重新创建source都会造成crash。正确的方式是在resume状态下调用dispatch_source_cancel(source)释放当前的source。
 */

-(void)dispatch_source_t{
     
    /** 创建定时器对象
     * para1: DISPATCH_SOURCE_TYPE_TIMER 为定时器类型
     * para2-3: 中间两个参数对定时器无用
     * para4: 最后为在什么调度队列中使用
     */
    self.dispatch_source_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    /** 设置定时器
     * para2: 任务开始时间
     * para3: 任务的间隔
     * para4: 可接受的误差时间，设置0即不允许出现误差
     * Tips: 单位均为纳秒
     */
    dispatch_source_set_timer(self.dispatch_source_timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);
    /** 设置定时器任务
     * 可以通过block方式
     * 也可以通过C函数方式
     */
    dispatch_source_set_event_handler(self.dispatch_source_timer, ^{
        static int gcdIdx = 0;
        NSLog(@"GCD Method: %d", gcdIdx++);
        NSLog(@"%@", [NSThread currentThread]);
     
    });
    // 启动任务，GCD计时器创建后需要手动启动
    dispatch_resume(self.dispatch_source_timer);
}

- (void)dealloc
{
//    dispatch_suspend(self.dispatch_source_timer);// EXC_BAD_INSTRUCTION 崩溃
    dispatch_source_cancel(self.dispatch_source_timer);
    self.dispatch_source_timer = nil;
}


/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end
